import '../../style.scss'; import { cache } from 'react'; import { Metadata } from 'next'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { fetchBondDetail } from '@/lib/api/seibro'; import { codeLabel, formatAmountCompact, formatDecimal, formatRatePercent, formatShares, dateOrDash } from '@/lib/utils/market'; import { BOND_OPTION_LABEL, BOND_RATE_CHG_LABEL, BOND_GUARANTEE_LABEL, BOND_RANK_LABEL, BOND_INT_PAY_WAY_LABEL, BOND_INT_CYCLE_LABEL, BOND_INT_TIMING_LABEL, BOND_PARTICUL_KIND_LABEL, BOND_SECN_KACD_LABEL } from '@/types/seibro'; type Props = { params: Promise<{ isin: string; }>; }; // generateMetadata + 본문 이중 호출 방지 (요청 단위 dedupe) const getDetail = cache(async (isin: string) => { return await fetchBondDetail(isin); }); export async function generateMetadata({ params }: Props): Promise { const { isin } = await params; const res = await getDetail(isin); const name = res.success && res.data?.master?.name ? res.data.master.name : isin; return { title: `${name} 채권 상세 — 개미투자`, description: `${name}(${isin}) 채권 상세 — 만기·표면이자율·신용등급·이자지급·종가/YTM (SEIBro)` }; } export default async function BondDetailPage({ params }: Props) { const { isin } = await params; const res = await getDetail(isin); if (!res.success && res.status === 404) { return notFound(); } if (!res.success || !res.data) { return (
← 채권 목록
채권 정보를 불러오지 못했습니다. 잠시 후 다시 시도해 주세요.
); } const detail = res.data; const master = detail.master; const interest = detail.interest; const market = detail.market; const name = master?.name ?? detail.isin; const grades = [ { agency: 'KIS', grade: master?.kisGrade }, { agency: 'NICE', grade: master?.niceGrade }, { agency: 'SCI', grade: master?.sciGrade }, { agency: 'KR', grade: master?.krGrade } ].filter(c => c.grade); return (
← 채권 목록

{name}

{detail.isin} · 채권 상세 (SEIBro)
{/* KRX 최신 시세 */} {market && (

최신 시세 ({market.tradeDate} 기준)

종가
{formatDecimal(market.close)}
만기수익률(YTM)
{formatRatePercent(market.yieldToMaturity, 3)}
거래량
{formatShares(market.volume)}
)} {/* 채권 마스터 */} {master ? (

발행 정보

종목종류
{codeLabel(master.secnKacd, BOND_SECN_KACD_LABEL)}
발행일
{dateOrDash(master.issueDate)}
만기일
{dateOrDash(master.maturityDate)}
표면이자율
{formatRatePercent(master.couponRate, 3)}
통화
{master.currency ?? '—'}
최초발행금액
{formatAmountCompact(master.firstIssueAmount)}
발행잔액
{formatAmountCompact(master.issueRemaining)}
금리유형
{codeLabel(master.rateChangeTpcd, BOND_RATE_CHG_LABEL)}
보증
{codeLabel(master.guaranteeTpcd, BOND_GUARANTEE_LABEL)}
순위
{codeLabel(master.rankTpcd, BOND_RANK_LABEL)}
이자지급방법
{codeLabel(master.intPayWayTpcd, BOND_INT_PAY_WAY_LABEL)}
옵션
{codeLabel(master.optionTpcd, BOND_OPTION_LABEL)}
특이종류
{codeLabel(master.particularBondKindTpcd, BOND_PARTICUL_KIND_LABEL)}
조기상환
{master.forcedEarlyRedemption === null ? '—' : master.forcedEarlyRedemption ? '있음' : '없음'}
전자증권
{master.isElectronicSecurity === null ? '—' : master.isElectronicSecurity ? '전자' : '예탁'}
{grades.length > 0 && (
{grades.map(c => (
{c.agency} {c.grade}
))}
)}
) : (
채권 발행 마스터 정보가 없습니다 (KRX 시세만 수집됨).
)} {/* 이자지급 정보 */} {interest && (

이자지급

표면이자율
{formatRatePercent(interest.couponRate, 3)}
지급방법
{codeLabel(interest.intPayWayTpcd, BOND_INT_PAY_WAY_LABEL)}
지급주기
{interest.cycleTerms ?? '—'}{interest.cycleTpcd ? codeLabel(interest.cycleTpcd, BOND_INT_CYCLE_LABEL) : ''}
지급시기
{codeLabel(interest.payTimingTpcd, BOND_INT_TIMING_LABEL)}
직전 이표일
{dateOrDash(interest.beforeCouponDate)}
차기 이표일
{dateOrDash(interest.afterCouponDate)}
)}
); }